using System;
using System.Collections;
using System.Collections.Generic;
using LightCAD.MathLib;

namespace LightCAD.MathLib
{
    public class Plane
    {
        #region scope properties or methods
        //private static Vector3 _vector1 = new Vector3();
        //private static Vector3 _vector2 = new Vector3();
        //private static Matrix3 _normalMatrix = new Matrix3();
        private PlaneContext GetContext()
        {
            return ThreadContext.GetCurrThreadContext().PlaneCtx;
        }
        #endregion

        #region Properties

        public Vector3 Normal;
        public double Constant;
        #endregion

        #region constructor
        public Plane(Vector3 normal = null, double constant = 0)
        {
            if (normal == null) normal = new Vector3(1, 0, 0);
            // normal is assumed to be normalized
            this.Normal = normal;
            this.Constant = constant;
        }
        #endregion

        #region methods
        public Plane Set(Vector3 normal, double constant)
        {
            this.Normal.Copy(normal);
            this.Constant = constant;
            return this;
        }
        public Plane SetComponents(double x, double y, double z, double w)
        {
            this.Normal.Set(x, y, z);
            this.Constant = w;
            return this;
        }
        public Plane SetFromNormalAndCoplanarPoint(Vector3 normal, Vector3 point)
        {
            this.Normal.Copy(normal);
            this.Constant = -point.Dot(this.Normal);
            return this;
        }
        public Plane SetFromCoplanarPoints(Vector3 a, Vector3 b, Vector3 c)
        {
            var ctx = GetContext();
            var _vector1 = ctx._vector1;
            var _vector2 = ctx._vector2;
            Vector3 normal = _vector1.SubVectors(c, b).Cross(_vector2.SubVectors(a, b)).Normalize();
            // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
            this.SetFromNormalAndCoplanarPoint(normal, a);
            return this;
        }
        public virtual Plane Copy(Plane plane)
        {
            this.Normal.Copy(plane.Normal);
            this.Constant = plane.Constant;
            return this;
        }
        public Plane Normalize()
        {
            // Note: will lead to a divide by zero if the plane is invalid.
            double inverseNormalLength = 1.0 / this.Normal.Length();
            this.Normal.MulScalar(inverseNormalLength);
            this.Constant *= inverseNormalLength;
            return this;
        }
        public Plane Negate()
        {
            this.Constant *= -1;
            this.Normal.Negate();
            return this;
        }
        public double DistanceToPoint(Vector3 point)
        {
            return this.Normal.Dot(point) + this.Constant;
        }
        public double DistanceToSphere(Sphere sphere)
        {
            return this.DistanceToPoint(sphere.Center) - sphere.Radius;
        }
        public Vector3 ProjectPoint(Vector3 point, Vector3 target = null)
        {
            target = target ?? new Vector3();
            return target.Copy(point).AddScaledVector(this.Normal, -this.DistanceToPoint(point));

            //return target.copy(this.normal).multiplyScalar(-this.distanceToPoint(point)).add(point);
        }
        public Vector3 IntersectLine(Line3 line, Vector3 target = null)
        {
            target = target ?? new Vector3();
            var _vector1 = GetContext()._vector1;
            Vector3 direction = line.Delta(_vector1);
            double denominator = this.Normal.Dot(direction);
            if (denominator == 0)
            {
                // line is coplanar, return origin
                if (this.DistanceToPoint(line.Start) == 0)
                {
                    return target.Copy(line.Start);
                }
                // Unsure if this is the correct method to handle this case.
                return null;
            }
            double t = -(line.Start.Dot(this.Normal) + this.Constant) / denominator;
            if (t < 0 || t > 1)
            {
                return null;
            }
            return target.Copy(line.Start).AddScaledVector(direction, t);
            //return target.copy(direction).multiplyScalar(t).add(line.start);
        }
        public bool IntersectsLine(Line3 line)
        {
            // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
            double startSign = this.DistanceToPoint(line.Start);
            double endSign = this.DistanceToPoint(line.End);
            return (startSign < 0 && endSign > 0) || (endSign < 0 && startSign > 0);
        }
        public bool IntersectsBox(Box3 box)
        {
            return box.IntersectsPlane(this);
        }
        public bool IntersectsSphere(Sphere sphere)
        {
            return sphere.IntersectsPlane(this);
        }
        public Vector3 CoplanarPoint(Vector3 target)
        {
            return target.Copy(this.Normal).MulScalar(-this.Constant);
        }
        public Plane ApplyMatrix4(Matrix4 matrix, Matrix3 optionalNormalMatrix = null)
        {
            var ctx = GetContext();
            var _normalMatrix = ctx._normalMatrix;
            var _vector1 = ctx._vector1;
            Matrix3 normalMatrix = optionalNormalMatrix ?? _normalMatrix.GetNormalMatrix(matrix);
            Vector3 referencePoint = this.CoplanarPoint(_vector1).ApplyMatrix4(matrix);
            Vector3 normal = this.Normal.ApplyMatrix3(normalMatrix).Normalize();
            this.Constant = -referencePoint.Dot(normal);
            return this;
        }
        public Plane Translate(Vector3 offset)
        {
            this.Constant -= offset.Dot(this.Normal);
            return this;
        }
        public virtual bool Equals(Plane plane)
        {
            return plane.Normal.Equals(this.Normal) && (plane.Constant == this.Constant);
        }
        public virtual Plane Clone()
        {
            return new Plane().Copy(this);
        }
        #endregion

    }
    public sealed class PlaneContext
    {
        public readonly Vector3 _vector1 = new Vector3();
        public readonly Vector3 _vector2 = new Vector3();
        public readonly Matrix3 _normalMatrix = new Matrix3();
    }

}
